/* 异常情况分析
 * 1. transactionManager.commit(Boolean); 没来得及执行宕机或日志断开无法更新
 *    transactionManager.rollback(Boolean);
 *       TYPE:1 STATUS:1  RESULT:0
 *       TYPE:2 STATUS:1  RESULT:0
 *       TYPE:2 STATUS:1  RESULT:0 --->  check  true:1 false:-1
 * 2. transactionManager.commit()
 *    transactionManager.rollback()  没来得及执行宕机或日志断开无法更新
 *       TYPE:1 STATUS:2|3  RESULT:0
 *       TYPE:2 STATUS:1    RESULT:0
 *       TYPE:2 STATUS:1    RESULT:0 --->  check  true:1 false:-1
 * 3.  transaction.commit()
 *     transaction.rollback()  for 中某个出错
 *       TYPE:1 STATUS:2|3  RESULT:1
 *       TYPE:2 STATUS:2|3  RESULT:1
 *       TYPE:2 STATUS:1    RESULT:0
 *       if(STATUS==1) do
 *       if(STATUS==2|3) do check  true:1 false:-1
 * 4.RESULT=-1 重试
 */
package com.wanrue.tcc.recover;

import com.wanrue.tcc.context.Context;
import com.wanrue.tcc.context.Transaction;
import com.wanrue.tcc.enums.Result;
import com.wanrue.tcc.enums.Role;
import com.wanrue.tcc.enums.Status;
import com.wanrue.tcc.repository.Repository;
import lombok.Setter;
import lombok.extern.log4j.Log4j;

import javax.annotation.Resource;
import java.util.List;

/**
 * 事务恢复
 * Created by wenjie on 10/25/18.
 */
@Log4j
@Setter
public class TransactionRecovery {

    @Resource
    private Repository repository;

    private RecoverConfig recoverConfig;

    @Resource
    private AbstractCheckService checkService;

    public void recover() {
        log.debug("recover start ....");
        recoverTransactions();
        log.debug("recover end ....");
        // 这里必须在同一个 线程中 不然 redis findAllUnmodifiedSince会有空key
        log.debug("clean start ....");
        repository.delete();
        log.debug("clean end ....");
    }

    private void recoverTransactions() {
        List<RecoveryInfo> transactions = repository.findAllUnmodifiedSince();
        int maxRetryCount = recoverConfig.getMaxRetryCount();
        int delay = recoverConfig.getDelay();
        for (RecoveryInfo info : transactions) {
            //时间没到间隔一定时间执行0 2 4 8 16 32.....
            if ((info.getLastUpdateTime().getTime() + (info.getCount() + 1) * delay * 1000) > System.currentTimeMillis()) {
                continue;
            }
            if (info.getCount() > maxRetryCount) {
                continue;
            }
            if (info.getRole() == Role.PARTICIPANT) {
                if (info.getMresult() == Result.SUCCESS) {
                    if (info.getMstatus() == info.getStatus() && info.getResult() == Result.INITIAL && !recoverConfig.isManual()) {//未知状态需要check
                        Context context = new Context(info.getXid(), info.getStatus());
                        Result result = checkService.checkStatus(context);
                        repository.update(info, result);
                    }
                }
            }
            if (info.getRole() == Role.INITIATOR) {
                if (info.getResult() == Result.INITIAL && !recoverConfig.isManual()) {//未知状态需要check
                    Context context = new Context(info.getXid(), info.getStatus());
                    Result result = checkService.checkStatus(context);
                    repository.update(info, result);
                }
                if (info.getStatus() == Status.TRYING) {//发起者是try result=1 commit ||  result=-1  rollback
                    if (info.getResult() == Result.SUCCESS) {
                        process(info, Status.CONFIRMING);
                    } else if (info.getResult() == Result.FAILURE) {
                        process(info, Status.CANCELLING);
                    }
                }
            }
            if (info.getResult() == Result.FAILURE && info.getStatus() != Status.TRYING) {// 不是try才能根据当前状态重试
                process(info, info.getStatus());
            }
        }
    }

    /**
     * 执行对应的操作恢复事务
     *
     * @param transaction 事务
     * @param status      操作
     */
    private void process(Transaction transaction, Status status) {
        try {
            transaction.setStatus(status);
            transaction.setCount(transaction.getCount() + 1);
            int update = repository.update(transaction, Result.INITIAL);//集群环境恢复情况下 乐观锁防止并发
            if (update <= 0) return;
            if (status == Status.CONFIRMING) {//提交
                transaction.commit();
                repository.update(transaction, Result.SUCCESS);
            } else if (status == Status.CANCELLING) {//回滚
                transaction.rollback();
                repository.update(transaction, Result.SUCCESS);
            }
        } catch (Exception e) {
            repository.update(transaction, Result.FAILURE);
            log.error(String.format("recover failed, txid:%s, status:%s,retried count:%d", transaction.getXid(), transaction.getStatus().value(), transaction.getCount()), e);
        }
    }
}
